package gestao_proj;

import java.util.LinkedList;
import java.util.TreeSet;

/**
 * Representacao do Projecto
 * 
 * @author Omar Castro
 * @author Rolando Pereira
 * 
 */
public class Projecto {
	protected String nome;
	public LinkedList<Tarefa> tarefas = new LinkedList<Tarefa>();

	public Projecto() {
		tarefas.add(new Tarefa("INICIO", 0));
		tarefas.add(new Tarefa("FIM", 0));

		nome = "Projecto sem nome";
	}

	public Projecto(String name) {
		this();
		nome = name;
	}

	public Projecto(String nome, LinkedList<Tarefa> tarefas) {
		this.nome = nome;
		this.tarefas = tarefas;
	}

	/**
	 * Permite incluir uma tarefa ao projecto.<br>
	 * Caso a Tarefa nao depende de qualquer tarefa, dependera da tarefa
	 * inicial.<br>
	 * Caso tambem nao tenha filhos a tarefa final dependera dela.
	 * 
	 * @param tar
	 *            - Tarefa incluida
	 * @since a tarefa final sempre estara no fim, ele sera adicionado atras da
	 *        tarefa "FIM"
	 */
	public void add(Tarefa tar) {
		if (tar.precedencias.isEmpty())
			tar.add_dependences(tarefas.getFirst());
		if (tar.filhos.isEmpty())
			tarefas.getLast().add_dependences(tar);
		tarefas.add(tarefas.size() - 1, tar);
	}

	/**
	 * Identifica as tarefas q vao depender do fim
	 * 
	 * @param tar
	 *            - Tarefas que serao dependentes
	 */
	public void setfim(Tarefa... tar) {
		for (int i = 0; i != tar.length; i++)
			tarefas.getLast().add_dependences(tar[i]);
	}

	/**
	 * Permite obter o tempo minimo que e necessario para completar o projecto.<br>
	 * ECT = Earliest Completion Time
	 * 
	 * @return tempo minimo necessario para completar o projecto
	 */
	public int getECT() {
		return getECT(tarefas.getLast());
	}

	// Metodo recursivo que calcula o ECT para uma dada tarefa
	public int getECT(Tarefa tar) {
		if (tar == tarefas.getFirst())
			return 0;

		else {
			// Precisamos de ir guardando os valores que vamos obtendo
			int[] temp_valores_ect = new int[tar.precedencias.size()];
			int it = 0;
			for (Tarefa k : tar.precedencias) {
				temp_valores_ect[it] = getECT(k) + tar.getduracao();
				it++;
			}

			// Quando chegamos aqui ja devemos de ter os valores necessarios
			// para calcular o ECT desta tarefa
			int maximo = -1;

			for (int k = 0; k != temp_valores_ect.length; k++) {
				if (temp_valores_ect[k] > maximo) {
					maximo = temp_valores_ect[k];
				}
			}

			return maximo;
		}
	}

	/**
	 * Permite obter o tempo maximo que e necessario para completar o projecto.<br>
	 * LCT = Longest Completion Time
	 * 
	 * @return tempo maximo necessario para completar o projecto
	 */
	public int getLCT() {
		return getLCT(tarefas.getFirst());
	}

	public int getLCT(Tarefa tar) {
		if (tar == tarefas.getLast()) {
			return getECT();
		} else {
			int[] temp_valores_lct = new int[tar.filhos.size()];
			int it = 0;
			for (Tarefa k : tar.filhos) {
				temp_valores_lct[it] = getLCT(k) - k.getduracao();
				it++;
			}

			int minimo = Integer.MAX_VALUE;

			for (int k = 0; k != temp_valores_lct.length; k++) {
				if (temp_valores_lct[k] < minimo) {
					minimo = temp_valores_lct[k];
				}
			}

			return (minimo == Integer.MAX_VALUE) ? 0 : minimo;
		}
	}

	/**
	 * Calcula as folgas nas actividades usando as funcoes LCT e ECT
	 */
	public int calcular_slack() {
		return calcular_slack(tarefas.getFirst());
	}

	public int calcular_slack(Tarefa tar) {
		return getLCT(tar) - getECT(tar);
	}

	/**
	 * Esta funcao e usada para determinar as tarefas criticas do projecto
	 * 
	 * @return array contendo as tarefas criticas
	 */

	public LinkedList<Tarefa> getTarefasCriticas() {
		LinkedList<Tarefa> return_value = new LinkedList<Tarefa>();

		for (Tarefa k : tarefas) {
			if (calcular_slack(k) == 0)
				return_value.add(k);
		}

		return return_value;
	}

	/**
	 * Ordena o vector tarefas topologicamente (a lista e ordenada in-place)
	 * 
	 */
	public void OrdenarTopo() {
		LinkedList<Tarefa> lista_ord_topo = new LinkedList<Tarefa>();

		while (!tarefas.isEmpty()) {
			for (Tarefa i : tarefas) {
				if (i.precedencias.isEmpty()) {
					// Sem arestas de chegada
					lista_ord_topo.add(i);

					// Temos que remover a tarefa TAR dos seus filhos
					while (!i.filhos.isEmpty()) {
						i.filhos.first().remove_dependences();
					}

					tarefas.remove(i);

					// Fazer o ciclo for comecar de novo
					break;
				}
			}
		}

		tarefas = lista_ord_topo;
	}

	/**
	 * Devolve um projecto em forma de string
	 * 
	 * Nota: isto e usado apenas para o teste JUnit
	 * 
	 */
	public String toString() {
		String return_value = new String();

		return_value += nome + "\n";
		for (Tarefa k : tarefas) {
			return_value += k.toString() + "\n";
		}

		return return_value;
	}
}

/**
 * A classe Tarefa e usada para determinar cada tarefa num projecto
 * 
 * @author Rolando Pereira
 * 
 */
class Tarefa implements Comparable<Object> {
	private String nome;
	private int duracao;
	protected TreeSet<Tarefa> precedencias = new TreeSet<Tarefa>();
	protected TreeSet<Tarefa> filhos = new TreeSet<Tarefa>();

	/**
	 *Cria uma tarefa sem dependecias
	 * 
	 * @param nome
	 *            - nome da tarefa
	 * @param duracao
	 *            - duracao da tarefa
	 */
	public Tarefa(String nome, int duracao) {
		this.nome = nome;
		this.duracao = duracao;
	}

	/**
	 * Cria uma tarefa com dependecias
	 * 
	 * @param nome
	 *            - nome da tarefa
	 * @param duracao
	 *            - duracao da tarefa
	 * @param tar
	 *            - lista de tarefas dependentes
	 */
	public Tarefa(String nome, int duracao, Tarefa... tar) {
		this.nome = nome;
		this.duracao = duracao;
		for (int i = 0; i != tar.length; i++)
			add_dependences(tar[i]);
	}

	/**
	 * Adiciona dependencias a uma tarefa
	 * 
	 * 
	 * @param tar
	 *            - tarefa a ser dependente
	 */
	public void add_dependences(Tarefa tar) {
		for (Tarefa i : tar.precedencias)
			if (precedencias.contains(i)) {
				precedencias.remove(i);
				i.filhos.remove(this);
			}
		precedencias.add(tar);

		for (Tarefa i : tar.filhos)
			if (filhos.contains(i)) {
				filhos.remove(i);
				i.precedencias.remove(this);
			}

		tar.filhos.add(this);

	}

	/**
	 * Remove dependencias a uma tarefa
	 * 
	 * @param tar
	 *            - lista de tarefas dependentes
	 */
	public void remove_dependences() {
		// Remover esta tarefa das suas precedencias
		while (!precedencias.isEmpty()) {
			for (Tarefa k : precedencias) {
				k.filhos.remove(this);
				precedencias.remove(k);

				// O iterador ja nao e valido devido ao remove, temos que
				// comecar de novo
				break;
			}
		}

		// Remover esta tarefa como precedencias dos seus filhos
		while (!filhos.isEmpty()) {
			for (Tarefa k : filhos) {
				k.precedencias.remove(this);
				filhos.remove(k);

				// O iterador ja nao e valido devido ao remove, temos que
				// comecar de novo
				break;
			}
		}
	}

	/**
	 * Como as tarefas estarao dentro de TreeSets � usado para comparar os
	 * elementos
	 */
	public int compareTo(Object arg0) {
		Tarefa temp = (Tarefa) arg0;
		return (duracao != temp.duracao) ? ((Integer) duracao)
				.compareTo(temp.duracao) : nome.compareTo(temp.nome);
	}

	/**
	 * Busca o nome da tarefa
	 * 
	 * @return nome da tarefa
	 */
	public String getnome() {
		return nome;
	}

	/**
	 * Busca a duracao da tarefa
	 * 
	 * @return duracao da tarefa
	 */
	public int getduracao() {
		return duracao;
	}

	/**
	 * Mostra toda a informacao da String
	 * 
	 * @return String com toda a informacao
	 */
	public String toString() {
		String return_value = getnome() + "/" + getduracao() + "/";

		for (Tarefa k : precedencias) {
			return_value += k.getnome() + ":";
		}

		return_value += "/";

		for (Tarefa k : filhos) {
			return_value += k.getnome() + ":";
		}

		return return_value;

	}
}
